/*
 * @(#)GatewayConfiguration.java 2021年3月12日
 *
 * Copyright (c) 2010 by rayootech.com. All rights reserved.
 */
package com.nuctech.gateway.config;

import java.nio.charset.StandardCharsets;
import java.util.Collections;
import java.util.List;
import java.util.Map;
import java.util.Set;

import javax.annotation.PostConstruct;

import org.springframework.beans.factory.ObjectProvider;
import org.springframework.cloud.gateway.filter.GlobalFilter;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.Ordered;
import org.springframework.core.annotation.Order;
import org.springframework.http.HttpStatus;
import org.springframework.http.MediaType;
import org.springframework.http.codec.ServerCodecConfigurer;
import org.springframework.web.reactive.function.BodyInserters;
import org.springframework.web.reactive.function.server.ServerResponse;
import org.springframework.web.reactive.result.view.ViewResolver;
import org.springframework.web.server.ServerWebExchange;

import com.alibaba.csp.sentinel.adapter.gateway.common.SentinelGatewayConstants;
import com.alibaba.csp.sentinel.adapter.gateway.common.api.ApiDefinition;
import com.alibaba.csp.sentinel.adapter.gateway.common.api.ApiPathPredicateItem;
import com.alibaba.csp.sentinel.adapter.gateway.common.api.GatewayApiDefinitionManager;
import com.alibaba.csp.sentinel.adapter.gateway.common.rule.GatewayFlowRule;
import com.alibaba.csp.sentinel.adapter.gateway.common.rule.GatewayRuleManager;
import com.alibaba.csp.sentinel.adapter.gateway.sc.SentinelGatewayFilter;
import com.alibaba.csp.sentinel.adapter.gateway.sc.callback.BlockRequestHandler;
import com.alibaba.csp.sentinel.adapter.gateway.sc.callback.GatewayCallbackManager;
import com.alibaba.csp.sentinel.adapter.gateway.sc.exception.SentinelGatewayBlockExceptionHandler;
import com.google.common.collect.Maps;
import com.google.common.collect.Sets;

import reactor.core.publisher.Mono;

/**
 * 基于Sentinel的Gateway限流是通过其提供的Filter来完成的，使用时只需要注入对应的SentinelGatewayFilter实
 * 例以及SentinelGatewayBlockExceptionHandler实例即可
 * 
 * @author lilu
 * @date 2021年3月12日
 * @since 1.0.0
 */
@Configuration
public class GatewayConfiguration {
	private final List<ViewResolver> viewResolvers;
	
	private final ServerCodecConfigurer serverCodecConfigurer;

	public GatewayConfiguration(ObjectProvider<List<ViewResolver>> viewResolversProvider, ServerCodecConfigurer serverCodecConfigurer) {
		this.viewResolvers = viewResolversProvider.getIfAvailable(Collections::emptyList);
		this.serverCodecConfigurer = serverCodecConfigurer;
	}
	
	// 初始化一个全局限流过滤器
	@Bean
	@Order(Ordered.HIGHEST_PRECEDENCE)
	public GlobalFilter sentinelGatewayFilter() {
		return new SentinelGatewayFilter();
	}
	
	/**
	 * 配置初始化限流参数
	 * 
	 * @author lilu
	 * @date 2021年3月12日 上午11:20:23
	 * @since 1.0.0
	 */
	@PostConstruct
	public void initGatewayRules() {
		// 针对route维度的限流
//		Set<GatewayFlowRule> rules = Sets.newHashSet();
//		rules.add(new GatewayFlowRule()
//			.setResource("product_route")    // 资源名称，对应的路由id
//			.setCount(1)                     // 限流阈值
//			.setIntervalSec(1)               // 统计时间窗口，单位是秒，默认1秒
//		);
//		GatewayRuleManager.loadRules(rules);
		
		// 针对自定义API分组维度的限流
		Set<GatewayFlowRule> rules = Sets.newHashSet();
		rules.add(new GatewayFlowRule()
			.setResource("product_api1")     // 资源名称，对应的路由id
			.setCount(1)                     // 限流阈值
			.setIntervalSec(1)               // 统计时间窗口，单位是秒，默认1秒
		);
		rules.add(new GatewayFlowRule()
			.setResource("product_api2")     // 资源名称，对应的路由id
			.setCount(1)                     // 限流阈值
			.setIntervalSec(1)               // 统计时间窗口，单位是秒，默认1秒
		);
		GatewayRuleManager.loadRules(rules);
	}
	
	/**
	 * 自定义API分组限流(自定义API分组是一种更细粒度的限流规则定义)
	 * 
	 * @author lilu
	 * @date 2021年3月12日 上午11:21:34
	 * @since 1.0.0
	 */
	@PostConstruct
	public void initCustomizedApis() {
		Set<ApiDefinition> definitions = Sets.newHashSet();
		
		definitions.add(new ApiDefinition()
			.setApiName("product_api1")
			.setPredicateItems(Sets.newHashSet(
				new ApiPathPredicateItem().setPattern("/product-serv/product/api1/**").setMatchStrategy(SentinelGatewayConstants.URL_MATCH_STRATEGY_PREFIX)
			))
		);
		definitions.add(new ApiDefinition()
			.setApiName("product_api2")
			.setPredicateItems(Sets.newHashSet(
				new ApiPathPredicateItem().setPattern("/product-serv/product/api2/**").setMatchStrategy(SentinelGatewayConstants.URL_MATCH_STRATEGY_PREFIX)
			))
		);
		
		GatewayApiDefinitionManager.loadApiDefinitions(definitions);
	}

	// 配置限流异常处理器
	@Bean
	@Order(Ordered.HIGHEST_PRECEDENCE)
	public SentinelGatewayBlockExceptionHandler sentinelGatewayBlockExceptionHandler() {
		return new SentinelGatewayBlockExceptionHandler(viewResolvers, serverCodecConfigurer);
	}
	
	// 自定义限流异常页面
	@PostConstruct
	public void initBlockHandlers() {
		BlockRequestHandler blockRequestHandler = new BlockRequestHandler() {
			@Override
			public Mono<ServerResponse> handleRequest(ServerWebExchange exchange, Throwable throwable) {
				Map<String, Object> map = Maps.newLinkedHashMap();
				map.put("code", 0);
				map.put("message", "接口被限流");
				return ServerResponse
						.status(HttpStatus.OK)
						.contentType(new MediaType(MediaType.APPLICATION_JSON, StandardCharsets.UTF_8))
						.body(BodyInserters.fromValue(map));
			}
		};
		GatewayCallbackManager.setBlockHandler(blockRequestHandler);
	}
}
